package com.xinsoft.websocket;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.websocket.Session;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author Li9527
 * @Description 会话管理
 * @Date 2023/4/23 9:15
 **/
@Component
@Slf4j
public class WebSocketSessionHolderManager {

    /**
     * 存放<用户id，Session>
     */
    private Map<String, WebSocketSessionHolder> SESSION_MAP;

    /**
     * 延迟线程池
     */
    private ScheduledThreadPoolExecutor executor;

    /**
     * 最大连接数
     */
    @Value("${websocket.max-active}")
    @Getter
    private int maxActive;

    /**
     * 连接心跳超时时间
     */
    @Value("${websocket.timeout}")
    @Getter
    private int timeout;

    /**
     * 初始化
     *
     * @description
     * @param :
     * @author Uyuhz
     * @date 2021/5/14 13:57
     * @return void
     */
    @PostConstruct
    private void init() {
        // 初始化SESSION_MAP
        SESSION_MAP = new ConcurrentHashMap<>(maxActive);
        // 初始化心跳检测线程
        executor = new ScheduledThreadPoolExecutor(2, r -> {
            Thread thread = new Thread(r);
            thread.setDaemon(true);
            thread.setName("websocket-monitor-thread");
            return thread;
        });
        // 启动心跳监测
        startHeartbeatMonitor();
    }

    /**
     * 发送消息
     *
     * @description
     * @param userId:  用户id
     * @param message: 消息
     * @author Uyuhz
     * @date 2021/5/14 13:42
     * @return java.util.concurrent.Future<java.lang.Void>
     */
    public Future<Void> sendMessage(String userId, String message) {
        WebSocketSessionHolder holder = SESSION_MAP.get(userId);
        if (holder == null || holder.getSession() == null || !holder.getSession().isOpen()) {
//            throw new RuntimeException("该用户不在线");
            log.info("该用户不在线：uid=" + userId);
            return null;
        }else {
            return holder.getSession().getAsyncRemote().sendText(message);
        }
    }

    /**
     * 保存会话持有对象
     *
     * @description 当SESSION_MAP中的连接数超过最大连接数时拒绝保存连接
     * @param userId: 用户id
     * @param holder: 会话持有对象
     * @author Uyuhz
     * @date 2021/5/14 10:26
     * @return void
     */
    public boolean save(String userId, WebSocketSessionHolder holder) {
        SESSION_MAP.put(userId, holder);
        return true;
    }

    /**
     * 移除会话持有对象
     *
     * @description
     * @param userId: 用户id
     * @author Uyuhz
     * @date 2021/5/14 10:24
     * @return void
     */
    public void remove(String userId) {
        SESSION_MAP.remove(userId);
    }

    /**
     * 移除会话持有对象
     *
     * @description
     * @param session: 会话
     * @author Uyuhz
     * @date 2021/5/14 11:57
     * @return void
     */
    public void remove(Session session) {
        WebSocketSessionHolder holder = get(session);
        if (holder != null) {
            remove(holder.getUserId());
        }
    }

    /**
     * 获取会话持有对象
     *
     * @description
     * @param userId: 用户id
     * @author Uyuhz
     * @date 2021/5/14 10:29
     * @return cn.o.baseline.modules.websocket.WebSocketSession
     */
    public WebSocketSessionHolder get(String userId) {
        return SESSION_MAP.get(userId);
    }

    /**
     * 获取会话持有对象
     *
     * @description
     * @param session: 会话
     * @author Uyuhz
     * @date 2021/5/14 11:10
     * @return cn.o.baseline.modules.websocket.WebSocketSessionHolder
     */
    public WebSocketSessionHolder get(Session session) {
        for (Map.Entry<String, WebSocketSessionHolder> entry : SESSION_MAP.entrySet()) {
            WebSocketSessionHolder holder = entry.getValue();
            // 只需要比较是否是同引用
            if (holder.getSession() == session) {
                return SESSION_MAP.get(entry.getKey());
            }
        }
        return null;
    }

    /**
     * 群发自定义消息
     */
    public void sendBroadCast(String message) {
        for (Map.Entry<String, WebSocketSessionHolder> entry : SESSION_MAP.entrySet()) {
            WebSocketSessionHolder holder = entry.getValue();
            holder.getSession().getAsyncRemote().sendText(message);
        }
    }
    /**
     * 心跳监测
     *
     * @description
     * @param :
     * @author Uyuhz
     * @date 2021/5/14 14:42
     * @return void
     */
    private void startHeartbeatMonitor() {
        executor.schedule(() -> {

            // 当次开始时，先开启下一次任务，防止实际执行时间过长导致不准时
            startHeartbeatMonitor();

            for (Map.Entry<String, WebSocketSessionHolder> entry : SESSION_MAP.entrySet()) {
                WebSocketSessionHolder holder = entry.getValue();
                if (holder != null) {
                    // 上次心跳时间是否已经大于超时时间
                    long seconds = DateUtil.between(DateUtil.date(), holder.getLastPingTime(), DateUnit.SECOND);
                    if (seconds >= timeout) {
                        // 超时关闭连接
                        try {
                            Session session = holder.getSession();
                            if (session.isOpen()) {
                                if (log.isDebugEnabled()) {
                                    log.debug("userId= {} 心跳超时，关闭连接", holder.getUserId());
                                }
                                holder.getSession().close();
                            }
                        } catch (IOException e) {
                            log.error("WebSocket连接超时，关闭时异常", e);
                        }
                    }
                }
            }

        }, 30, TimeUnit.SECONDS);
    }
}
